/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package dale2507.gates.gate;

import dale2507.gates.GateDirectory;
import dale2507.gates.Messenger;
import dale2507.gates.data.Settings;
import dale2507.gates.data.permissions.ICreationPermissions;
import dale2507.gates.data.permissions.Permissions;
import dale2507.gates.exceptions.*;
import dale2507.gates.misc.DirectionSensitiveLocation;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;

/**
 * Class for easily creating the gate class and it's components. To succeed 3 componenets must be added or discovered, the ring, dhd (chest) and display (sign).
 * @author Dale2507
 */
public class GateBuilder {
    
    private IRing ring;
    private DHD dhd;
    private Display display;
    private GateSettings settings;
    
    public GateBuilder() {
        
    }
    
    /**
     * Creates a ring at the designated location
     * @param center Location where the center of the ring should be
     * @return Returns this class so that functions can be joined together
     * @throws VerificationException Thrown if the ring cannot be created
     */
    public GateBuilder spawnRing(DirectionSensitiveLocation center) throws VerificationException {
        Block[] blocks = Ring.getIntendedRingBlocks(center);
        Material m = Settings.getInstance().creation.getGateMaterial();
        for (Block b : blocks) {
            b.setType(m);
        }
        
        this.ring = new Ring(center);
        
        return this;
    }
    
    /**
     * Searches for a ring that already exists. The search area is defined by the config file.
     * @param searchCenter Center to search from
     * @return Returns this class so that functions can be joined together
     * @throws VerificationException Thrown if the ring cannot be created
     */
    public GateBuilder findRing(Block searchCenter) {
        int hori = Settings.getInstance().creation.searchArea().getHorizontal();
        int vert = Settings.getInstance().creation.searchArea().getVertical();
        int c = 0;
        int highest = Math.max(hori, vert);
        Material m = Settings.getInstance().creation.getGateMaterial();
        
        while (this.ring == null && c < highest) {
            for (int y=(0-Math.min(c, vert)); (y<=Math.min(c, vert) && dhd == null); y++) {
                for (int x=(0-Math.min(c, hori)); (x<=Math.min(c, hori) && dhd == null); x++) {
                    for (int z=(0-Math.min(c, hori)); (z<=Math.min(c, hori) && dhd == null); z++) {
                        // Check at least one is equal to c to ensure no block is being checked twice.
                        int high = Math.max(Math.max(Math.abs(x), Math.abs(y)), Math.abs(z));
                        if (this.ring == null && high == c && searchCenter.getRelative(x,y,z).getType().equals(m)) {
                            this.ring = getRing(searchCenter.getRelative(x, y, z));
                        }
                    }
                }
            }
            
            c++;
        }
        
        return this;
    }
    
    private Ring getRing(Block start) {
        Material m = Settings.getInstance().creation.getGateMaterial();
        
        for (int y=-6; y<=6; y+=12) {
            if (start.getRelative(0, y, 0).getType().equals(m)) {
                // Is vertical up
                Location center = start.getRelative(0, (y/2), 0).getLocation();
                if (center.getBlock().getRelative(3, 0, 0).getType().equals(m)) {
                    // obsidian is on x so check that rotation
                    try {
                        return new Ring(center);
                    } catch (VerificationException ex) {}
                }
                if (center.getBlock().getRelative(0, 0, 3).getType().equals(m)) {
                    // obsidian is on z so check that rotation
                    center.setYaw(90f);
                    try {
                        return new Ring(center);
                    } catch (VerificationException ex) {}
                }
            }
            if (start.getRelative(y, 0, 0).getType().equals(m)) {
                // Is horizontal on x
                Location center = start.getRelative((y/2), 0, 0).getLocation();
                center.setPitch(90f);
                try {
                    return new Ring(center);
                } catch (VerificationException ex) {}
            }
            if (start.getRelative(0, 0, y).getType().equals(m)) {
                // Is horizontal on x
                Location center = start.getRelative(0, 0, (y/2)).getLocation();
                center.setPitch(90f);
                try {
                    return new Ring(center);
                } catch (VerificationException ex) {}
            }
        }
        
        return null;
    }

    /**
     * Gets the dhd (chest) which is currently assigned to the builder
     * @return DHD (chest) assigned to the builder
     */
    public DHD getDhd() {
        return dhd;
    }

    /**
     * Sets a preconfigured dhd (Chest) for the builder to use
     * @param dhd
     * @return Returns this class so that functions can be joined together
     */
    public GateBuilder setDhd(DHD dhd) {
        this.dhd = dhd;
        return this;
    }
    
    public GateBuilder findDHD(Block center) throws VerificationException {
        int hori = Settings.getInstance().creation.searchArea().getHorizontal();
        int vert = Settings.getInstance().creation.searchArea().getVertical();
        int c = 0;
        int highest = Math.max(hori, vert);
        
        while (dhd == null && c < highest) {
            for (int y=(0-Math.min(c, vert)); (y<=Math.min(c, vert) && dhd == null); y++) {
                for (int x=(0-Math.min(c, hori)); (x<=Math.min(c, hori) && dhd == null); x++) {
                    for (int z=(0-Math.min(c, hori)); (z<=Math.min(c, hori) && dhd == null); z++) {
                        // Check at least one is equal to c to ensure no block is being checked twice.
                        int high = Math.max(Math.max(Math.abs(x), Math.abs(y)), Math.abs(z));
                        if (this.dhd == null && high == c && center.getRelative(x,y,z).getType().equals(Material.CHEST)) {
                            dhd = new DHD(center.getRelative(x,y,z));
                        }
                    }
                }
            }
            
            c++;
        }
        
        return this;
    }

    public Display getDisplay() {
        return display;
    }

    public GateBuilder setDisplay(Display display) {
        this.display = display;
        return this;
    }

    public IRing getRing() {
        return this.ring;
    }

    public GateBuilder setRing(IRing ring) {
        this.ring = ring;
        return this;
    }

    public GateSettings getSettings() {
        return settings;
    }

    public GateBuilder setSettings(GateSettings settings) {
        this.settings = settings;
        return this;
    }
    
    public Gate build(Player p) throws GateBuilderException, VerificationException, PermissionException {
        if (display == null) throw new GateBuilderException("No display was added to the gate builder.");
        if (ring == null) {
            this.display.setError();
            throw new GateBuilderException("No ring was added to the gate builder.");
        }
        if (dhd == null) {
            this.display.setError();
            throw new GateBuilderException("No dhd was added to the gate builder.");
        }
        
        if (this.settings == null) {
            String owner = "";
            if (p != null) owner = p.getPlayerListName();
            this.settings = new GateSettings(this.display, this.dhd, owner);
        }
        
        Address a = this.settings.getAddress();
        if (!Settings.getInstance().creation.allowOutgoingOnly() && (a == null || a.isEmpty()))
            throw new GateBuilderException("Outgoing only gates are not allowed");
        
        int minChevrons = Settings.getInstance().creation.minimumChevrons();
        if (a != null && !a.isEmpty() && a.getItemCount() < minChevrons)
            throw new GateBuilderException("Gates must have at least " + minChevrons + " items in an address");
        
        if (this.settings.getAutoDialAddress() != null && !Settings.getInstance().creation.allowAutoDial())
            throw new GateBuilderException("Auto dial gates are not allowed");
        
        // Check permissions
        if (p != null) this.checkPermissions(p);
        
        // Create gate and add to directory
        Gate g = new Gate(settings, this.ring, this.dhd, this.display);
        GateDirectory gd = GateDirectory.getInstance();
        try {
            gd.addGate(g);
        } catch (GateDirectoryException ex) {
            this.display.setError();
            throw new GateBuilderException(ex);
        }
        
        if (p != null) this.dhd.empty();
        
        // If gate was built by player and is indefinite, dial the gate
        if (p != null && g.getSettings().isIndefinite() && 
                Settings.getInstance().creation.indefiniteConnectionItem() != null) {
            try {
                g.getActivator().activate(p);
            } catch (GateConnectionException | PermissionException ex) {
                Messenger.sendMessage(p, Messenger.ERROR, g.getSettings().getName() + " gate: " + ex.getMessage());
            }
        }
        
        return g;
    }

    private void checkPermissions(Player p) throws PermissionException {
        ICreationPermissions cp = Permissions.getPermissions().getCreationPermissions();
        
        switch(this.settings.getVisibility()) {
            case GateSettings.PUBLIC:
                if (!cp.allowGateCreatePublic(p)) throw new PermissionException("You are not allowed to create public gates");
                break;
            case GateSettings.HIDDEN:
                if (!cp.allowGateCreateHidden(p)) throw new PermissionException("You are not allowed to create hidden gates");
                break;
            case GateSettings.PRIVATE:
                if (!cp.allowGateCreatePrivate(p)) throw new PermissionException("You are not allowed to create private gates");
                break;
        }
        
        if (this.settings.getIrisKey() != null &&
                !cp.allowGateCreateWithIris(p))
            throw new PermissionException("You are not allowed to create gates with irises");
        
        if (this.settings.isIndefinite() == true &&
                !cp.allowGateCreateIndefinite(p))
            throw new PermissionException("You are not allowed to create gates with indefinite connections");
    }
    
}
